package cn.juque.workflow.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.juque.common.base.BaseOperator;
import cn.juque.common.base.PageInfo;
import cn.juque.common.exception.AppException;
import cn.juque.common.thread.BaseOperatorThreadLocal;
import cn.juque.workflow.api.enums.ProcessInstanceStatusEnum;
import cn.juque.workflow.api.enums.WorkflowMsgEnum;
import cn.juque.workflow.bo.*;
import cn.juque.workflow.bo.businesstask.BusinessTaskDetailBO;
import cn.juque.workflow.bo.businesstask.BusinessTaskSaveBO;
import cn.juque.workflow.bo.businesstask.BusinessTaskUpdateBO;
import cn.juque.workflow.converter.ProcessTaskConverter;
import cn.juque.workflow.domain.ProcessTaskDO;
import cn.juque.workflow.helper.ProcessInfoHelper;
import cn.juque.workflow.helper.TaskInfoHelper;
import cn.juque.workflow.service.IProcessTaskService;
import cn.juque.workflow.service.task.BusinessTaskRender;
import cn.juque.workflow.service.task.IBusinessTaskService;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.camunda.bpm.engine.HistoryService;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.TaskService;
import org.camunda.bpm.engine.history.HistoricActivityInstance;
import org.camunda.bpm.engine.history.HistoricProcessInstance;
import org.camunda.bpm.engine.history.HistoricTaskInstance;
import org.camunda.bpm.engine.history.HistoricTaskInstanceQuery;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.task.Task;
import org.camunda.bpm.engine.task.TaskQuery;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.instance.FlowNode;
import org.camunda.bpm.model.bpmn.instance.SequenceFlow;
import org.camunda.bpm.model.xml.instance.ModelElementInstance;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author juque
 * @version 1.0.0
 * <ul>
 *     <li>ProcessTaskServiceImpl</li>
 * </ul>
 * @date 2023-01-28 09:15:56
 **/
@Service("processTaskService")
public class ProcessTaskServiceImpl implements IProcessTaskService {

    @Resource
    private TaskService taskService;

    @Resource
    private HistoryService historyService;

    @Resource
    private RepositoryService repositoryService;

    @Resource
    private BusinessTaskRender businessTaskRender;

    @Resource
    private ProcessInfoHelper processInfoHelper;

    @Resource
    private TaskInfoHelper taskInfoHelper;

    @Resource
    private BaseOperatorThreadLocal baseOperatorThreadLocal;

    /**
     * 查询用户的代办
     *
     * @param processTaskDO 查询条件
     * @param pageInfo      分页信息
     * @return List
     */
    @Override
    public List<Task> list(ProcessTaskDO processTaskDO, PageInfo pageInfo) {
        TaskQuery taskQuery = this.taskService.createTaskQuery();
        ProcessTaskConverter.toProcessTaskDO(taskQuery, processTaskDO);
        taskQuery.orderByTaskCreateTime().desc();
        // 查询总数
        long count = taskQuery.count();
        pageInfo.setTotal(count);
        // 查询结果集
        int start = (pageInfo.getPage() - 1) * pageInfo.getLimit();
        int offset = pageInfo.getLimit();
        return taskQuery.listPage(start, offset);
    }

    /**
     * 查询详情
     *
     * @param processDefinitionKey 流程定义id
     * @param businessId           业务id
     * @return BusinessTaskDetailBO
     */
    @Override
    public BusinessTaskDetailBO<?> detail(String processDefinitionKey, String businessId) {
        IBusinessTaskService<?> businessTaskService = this.businessTaskRender.render(processDefinitionKey);
        BusinessTaskDetailBO<?> businessTaskDetailBO = businessTaskService.detail(businessId);
        if (CharSequenceUtil.isNotEmpty(businessId)) {
            HistoricProcessInstance historicProcessInstance = this.historyService.createHistoricProcessInstanceQuery()
                    .processDefinitionKey(processDefinitionKey).processInstanceBusinessKey(businessId).singleResult();
            businessTaskDetailBO.setProcessInstanceId(historicProcessInstance.getId());
        }
        return businessTaskDetailBO;
    }

    /**
     * 查询详情
     *
     * @param processDefinitionKey 流程定义id
     * @param taskId              任务ID
     * @return BusinessTaskDetailBO
     */
    @Override
    public BusinessTaskDetailBO<?> detailByTaskId(String processDefinitionKey, String taskId) {
        IBusinessTaskService<?> businessTaskService = this.businessTaskRender.render(processDefinitionKey);
        return businessTaskService.detailByTaskId(taskId);
    }

    /**
     * 加载bpmn图
     *
     * @param deployId 流程发布ID
     * @return BpmnBO
     */
    @Override
    public BpmnBO loadBpmnByDeployId(String deployId) {
        BpmnBO bpmnBO = new BpmnBO();
        bpmnBO.setResource(this.processInfoHelper.getResourceByDeployId(deployId));
        return bpmnBO;
    }

    /**
     * 加载bpmn图
     *
     * @param processInstanceId 实例ID
     * @return BpmnBO
     */
    @Override
    public BpmnBO loadBpmnByInstanceId(String processInstanceId) {
        BpmnBO bpmnBO = new BpmnBO();
        HistoricProcessInstance historicProcessInstance = this.historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        String processDefinitionId = historicProcessInstance.getProcessDefinitionId();
        bpmnBO.setResource(this.processInfoHelper.getResourceByProcessDefinitionId(processDefinitionId));
        // 查询已完成的节点
        List<HistoricActivityInstance> finished = this.processInfoHelper.listActivityInstance(processInstanceId, true);
        Set<String> highlightNodeSet = finished.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toSet());
        bpmnBO.setHighlightNodeList(CollUtil.newArrayList(highlightNodeSet));
        // 待完成节点
        List<HistoricActivityInstance> unfinished = this.processInfoHelper.listActivityInstance(processInstanceId, false);
        Set<String> waitingTodoSet = unfinished.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toSet());
        bpmnBO.setWaitingTodoNodeList(CollUtil.newArrayList(waitingTodoSet));
        // 我执行过的
        BaseOperator baseOperator = this.baseOperatorThreadLocal.get();
        List<HistoricTaskInstance> taskInstanceList = this.historyService.createHistoricTaskInstanceQuery()
                .taskAssignee(baseOperator.getUserId())
                .processInstanceId(processInstanceId).finished().list();
        Set<String> iDoSet = taskInstanceList.stream().map(HistoricTaskInstance::getTaskDefinitionKey).collect(Collectors.toSet());
        bpmnBO.setIDoNodeList(CollUtil.newArrayList(iDoSet));
        // 获取bpmn模型
        BpmnModelInstance bpmnModelInstance = this.repositoryService.getBpmnModelInstance(processDefinitionId);
        // 高亮线
        Set<String> highlightLineSet = CollUtil.newHashSet();
        // 完成节点间的分支线
        List<SequenceFlow> finishSequenceFlowList = this.processInfoHelper.listSequenceFlowByActivityInstance(bpmnModelInstance, finished);
        finishSequenceFlowList.forEach(f -> highlightLineSet.add(f.getId()));
        ModelElementInstance domInstance;
        FlowNode flowNode;
        // 完成节点和未完成节点的分支线
        Set<String> unFinishActivityIdSet = unfinished.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toSet());
        for (HistoricActivityInstance finishItem : finished) {
            // 根据key获取bpmn元素
            domInstance = bpmnModelInstance.getModelElementById(finishItem.getActivityId());
            // 转换成flowNode流程节点，获取输出线、输入线
            flowNode = (FlowNode) domInstance;
            Collection<SequenceFlow> outGoingList = flowNode.getOutgoing();
            // 循环当前节点向下的分支
            outGoingList.forEach(v -> {
                String targetId = v.getTarget().getId();
                // 待完成的分支
                if (unFinishActivityIdSet.contains(targetId)) {
                    highlightLineSet.add(v.getId());
                }
            });
        }
        bpmnBO.setHighlightLineList(CollUtil.newArrayList(highlightLineSet));
        return bpmnBO;
    }

    /**
     * 查询已办
     *
     * @param processTaskDO 查询条件
     * @param pageInfo      分页信息
     * @return List
     */
    @Override
    public List<HistoricTaskInstance> listFinish(ProcessTaskDO processTaskDO, PageInfo pageInfo) {
        HistoricTaskInstanceQuery historicTaskInstanceQuery = this.historyService.createHistoricTaskInstanceQuery();
        ProcessTaskConverter.toProcessTaskDO(historicTaskInstanceQuery, processTaskDO);
        historicTaskInstanceQuery.taskAssignee(processTaskDO.getTaskAssignee());
        historicTaskInstanceQuery.orderByTaskDueDate().desc();
        // 查询总数
        long count = historicTaskInstanceQuery.count();
        pageInfo.setTotal(count);
        // 查询结果集
        int start = (pageInfo.getPage() - 1) * pageInfo.getLimit();
        int offset = pageInfo.getLimit();
        return historicTaskInstanceQuery.listPage(start, offset);
    }

    /**
     * 创建实例
     *
     * @param saveBO 参数
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createInstance(InstanceCreateBO saveBO) {
        IBusinessTaskService<?> businessTaskService = this.businessTaskRender.render(saveBO.getProcessDefinitionKey());
        BusinessTaskSaveBO businessTaskSaveBO = new BusinessTaskSaveBO();
        businessTaskSaveBO.setEntity(saveBO.getJsonObject());
        String businessId = businessTaskService.createInstanceBefore(businessTaskSaveBO);
        // 是否发起流程
        if (!Boolean.TRUE.equals(saveBO.getStartTask())) {
            return;
        }
        InstanceStartBO startBO = new InstanceStartBO();
        BeanUtil.copyProperties(saveBO, startBO);
        startBO.setBusinessId(businessId);
        this.startInstance(startBO);
    }

    /**
     * 变更实例
     *
     * @param updateBO 参数
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateInstance(InstanceUpdateBO updateBO) {
        if (CharSequenceUtil.isNotEmpty(updateBO.getProcessInstanceId())) {
            // 只有驳回允许修改
            ProcessInstanceStatusEnum statusEnum = this.processInfoHelper.getInstanceStatus(updateBO.getProcessInstanceId());
            if (!ProcessInstanceStatusEnum.REJECT.getCode().equals(statusEnum.getCode())) {
                throw new AppException(WorkflowMsgEnum.UPDATE_FAIL_BY_NOT_REJECT);
            }
        }
        IBusinessTaskService<?> businessTaskService = this.businessTaskRender.render(updateBO.getProcessDefinitionKey());
        BusinessTaskUpdateBO businessTaskUpdateBO = new BusinessTaskUpdateBO();
        businessTaskUpdateBO.setEntity(updateBO.getJsonObject());
        String businessId = businessTaskService.update(businessTaskUpdateBO);
        // 是否发起流程
        if (!Boolean.TRUE.equals(updateBO.getStartTask())) {
            return;
        }
        InstanceStartBO startBO = new InstanceStartBO();
        BeanUtil.copyProperties(updateBO, startBO);
        startBO.setBusinessId(businessId);
        this.startInstance(startBO);
    }

    /**
     * 启动实例
     *
     * @param startBO 参数
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void startInstance(InstanceStartBO startBO) {
        BaseOperator baseOperator = this.baseOperatorThreadLocal.get();
        String processDefineKey = startBO.getProcessDefinitionKey();
        String businessId = startBO.getBusinessId();
        Map<String, Object> map = startBO.getVariableMap();
        String instanceId = this.processInfoHelper.createInstance(processDefineKey, businessId, baseOperator.getUserId(), map);
        // 是否跳过第一个节点
        if (Boolean.TRUE.equals(startBO.getSkipFirstTask())) {
            ProcessTaskDO taskQuery = new ProcessTaskDO();
            taskQuery.setProcessInstanceId(instanceId);
            List<Task> taskList = this.list(taskQuery, new PageInfo());
            Task task = taskList.get(0);
            TaskFinishParamBO paramBO = new TaskFinishParamBO();
            paramBO.setProcessDefinitionKey(startBO.getProcessDefinitionKey());
            paramBO.setTaskId(task.getId());
            paramBO.setTaskAssignee(baseOperator.getUserId());
            paramBO.setApproveRemark("自动完成节点");
            paramBO.setVariableMap(map);
            this.taskFinish(paramBO);
        }
        IBusinessTaskService<?> businessTaskService = this.businessTaskRender.render(processDefineKey);
        businessTaskService.startInstanceAfter(instanceId, businessId);
    }

    /**
     * 删除/作废
     *
     * @param processDefinitionKey 流程定义ID
     * @param instanceId           实例ID
     * @param deleteReason         删除原因
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteInstance(String processDefinitionKey, String instanceId, String deleteReason) {
        ProcessInstance processInstance = this.processInfoHelper.getInstance(instanceId);
        IBusinessTaskService<?> businessTaskService = this.businessTaskRender.render(processDefinitionKey);
        businessTaskService.delete(processInstance.getBusinessKey());
        this.processInfoHelper.deleteInstance(instanceId, deleteReason);
    }

    /**
     * 完成任务
     *
     * @param paramBO 参数
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void taskFinish(TaskFinishParamBO paramBO) {
        this.taskInfoHelper.taskFinish(paramBO);
        IBusinessTaskService<?> businessTaskService = this.businessTaskRender.render(paramBO.getProcessDefinitionKey());
        businessTaskService.taskFinishAfter(paramBO.getTaskId());
    }

    /**
     * 认领任务
     *
     * @param taskId       任务ID
     * @param taskAssignee 用户ID
     */
    @Override
    public void claimTask(String taskId, String taskAssignee) {
        // 校验候选人是否在范围内
        long count = this.taskService.createTaskQuery().taskId(taskId).taskCandidateUser(taskAssignee).count();
        if (!SqlHelper.retBool(count)) {
            throw new AppException(WorkflowMsgEnum.CANDIDATE_USER_OUT_LIMIT);
        }
        this.taskService.claim(taskId, taskAssignee);
    }
}
